<?php

abstract class CommonParser
{	
	//donde se mete el contenido literal inicial
	protected $content = "";
	//el innerContent - en los containers es igual al content; en los otros es el interior del tag
	protected $inner_content = "";
	//parsers de los subcontroles, indexados por id del subcontrol
	protected $subparsers = array();
	//almacén de tagInfos de lus runatserver encontrados (taqs normales y de userControls). Clave asociativa ID tag
	protected $tags;	
	//lista de los ids que corresponden a tags raíz
	protected $root_ids;
		
	
	//devuelvo mis subparsers recursivamente
	public function getAllSubparsers()
	{
		$ret = $this->subparsers;
		foreach($ret as $subp)
		{
			$ret = array_merge($ret, $subp->getAllSubparsers());
		}
		
		return $ret;
	}
	
	public function getOriginalContent()
	{	
		return $this->content;
	}
	
		
	//bueno, tal vez no. Esto podría generar el código de construcción en los predefinidos
	public abstract function getInitCode($rootParser);

	
	
	//este es el parseo común (identificar tags y crear subparsers)
	//el parse() de los descendientes hará cosas específicas (procesar atributos html..)
 	public function parse()
 	{
 		//global $debug_level;
 		 		 		
 		$this->findServerTags();
		$this->root_ids = $this->getRootTags();
		//$roots contiene ids
		
		//creo los subparsers, y los lanzo
		$this->subparsers = array();
		foreach($this->root_ids as $root)
		{
			$parser = array();			
			$parser = ParserFactory::GetParserFromTagInfo($this->tags[$root]);
			$this->subparsers[$root] = $parser;
			$parser->parse();
		}
 	}

	
	//Determina los tags "raíz" (no contenidos en otros)
	private function getRootTags()
	{
		$ret = array();
		foreach($this->tags as $tag)
		{
			if(sizeof($this->getAscendentTags($tag))==0)
				$ret[] = $tag["id"];
		}
		return $ret;
	}
	
	//determina todos los descendientes de un tag
	private function getNestedTags($tag)
	{
		$ret = array();
		foreach($this->tags as $candidate)
		{
			if(this::tagContains($tag, $candidate))
			{
				$ret[] = $candidate["id"];				
			}
		}
		return $ret;
	}
	
	//determina todos los ascendientes de un tag
	private function getAscendentTags($tag)
	{
		$ret = array();
		foreach($this->tags as $candidate)
		{
			if(CommonParser::tagContains($candidate,$tag))
			{
				$ret[] = $candidate["id"];				
			}
		}
		return $ret;
	}
	
	private static function tagContains($tagA, $tagB)
	{
		if($tagA["short_tag"]) return false;
		
		if($tagB["short_tag"])
			return ($tagA["tag_end"]<$tagB["tag_start"] && $tagA["close_start"]>$tagB["tag_end"]);
		else
			return ($tagA["tag_end"]<$tagB["tag_start"] && $tagA["close_start"]>$tagB["close_end"]);
		
	}
	
	
	
	
	/*
	Encuentra todos los tags runat="server" que haya en el marcado original, y crea un array asociativo de información para cada uno
	La clave del array son los ids de los tags
	*/
	protected function findServerTags()
	{
		global $debug_level;
		
		//tags que a veces no se cierran bien
		$funny_tags = array("img", "br", "hr");
 	
 		//localizar todas las movidas con runat="server"
 		$mark = "runat=\"server\"";
 		$posiciones = find_all($this->inner_content, $mark);
 		$this->tags = array();
 		$this->ucTags = array();
 		$found_ids = array();
 		//en cada tag runat server, localizar su < y su >
 		//el nombre del tag
 		//las posiciones de cierre tb
 		//si es autocontenido
 		foreach($posiciones as $pos)
 		{
 			$tag = array();
 			$tag["pos"] = $pos;
 			
 			//límites del de apertura
 			$tag["tag_start"] = strrpos(substr($this->inner_content, 0, $pos), "<");
 			$tag["tag_end"] = strpos($this->inner_content, ">", $pos);
 			
 			//el tag en sí
 			$rex = "/([\\w\\:]+?)[\\n\\s\\r\\f]/s";
 			
 			$cnt = preg_match($rex, substr($this->inner_content, $tag["tag_start"]), $match);
 			if($cnt==0) 
 				throw new Exception("Tag malformado");
 			$tag["tag"] = strtolower($match[1]);
 			
 			$tag["isUc"] = false;
 			if(strpos($tag["tag"], ":")) 
 			{
 				if($debug_level>0) echo "Encontrado tag de usuario ".$tag["tag"]."\n";
 				$tag["isUc"]=true;
			}
						 			
			//sacar el contenido literal
			$tag["literal"] = substr($this->inner_content, $tag["tag_start"], $tag["tag_end"]-$tag["tag_start"]+1);
			
			//verificar que tiene ID
			$rex = "/\\bid\\=\"(\w+?)\"/";
			$cnt = preg_match($rex, $tag["literal"], $match);
			if($cnt==0) 
				throw new Exception("Falta el atributo ID");
			$id = $match[1];
			if(in_array($id, $found_ids))
			{
				throw new Exception("ID duplicado: $id");
			}
			else
			{	
				$tag["id"] = $id;
				$found_ids[] = $id;
			}
			
			//ver si es autocontenido - prevenir ciertas putadas con img,br,hr,object y esas mierdas que a veces no se cierran 			
			$tag["short_tag"] = substr($tag["literal"], -2, 1)=="/";
			if(!$tag["short_tag"] && in_array($tag["tag"], $funny_tags))
				$tag["short_tag"] = true;
						
			//buscar el cierre
			if(!$tag["short_tag"])
			{
				/* Esto que voy a hacer es una chapuza, porque buscaré los literales <tag y </tag
					*/
				$rex_open = "/\\<".$tag["tag"]."[\\>\\n\\s\\r\\f]/";				
				$str_close = "</".$tag["tag"].">";
				$open_pos = rex_find_all($this->inner_content, $rex_open);
				$close_pos = find_all($this->inner_content, $str_close);
							
				if($debug_level>30)
				{
					echo "Open: ";
					print_r($open_pos);
					echo "Close: ";
					print_r($close_pos);
				}
				
				$siguientes = array();
				foreach($open_pos as $ix)
				{
					if($ix>$tag["tag_end"])					
						$siguientes[$ix] = "open";					
				}
				foreach($close_pos as $ix)
				{
					if($ix>$tag["tag_end"])					
						$siguientes[$ix] = "close";					
				}
				ksort($siguientes);
				
				if($debug_level>30)
				{
					echo "Siguientes: ";
					print_r($siguientes);
					echo "\n";
					foreach($siguientes as $key=>$val)
					{
						echo "$key: $val\n";
					}					
				}
				
				$opened = 1;
				$closed = 0;
				foreach($siguientes as $key=>$val)
				{
					if($val=="open") $opened++;
					else $closed++;
					
					if($closed==$opened)
						$tag["close_start"] = $key;
				}
				
				if(!$tag["close_start"]) throw new Exception("(".$this->id.") No se encuentra el tag de cierre para <".$tag["tag"].">");
								
				$tag["close_end"] = $tag["close_start"]+strlen("</".$tag["tag"].">"); 				
			}
			
			//rellernar el innerContent
			if($tag["short_tag"])
				$tag["inner_content"] = "";
			else
				$tag["inner_content"] = substr($this->inner_content, $tag["tag_end"]+1, ($tag["close_start"]-$tag["tag_end"])-1);
			
			//rellenar el outerContent
			if($tag["short_tag"])
				$tag["outer_content"] = $tag["literal"];
			else
				$tag["outer_content"] = substr($this->inner_content, $tag["tag_start"], ($tag["close_end"]-$tag["tag_start"]));
			
			if($debug_level>40)
			{
				echo "Resumen del tag: \n";
				print_r($tag);
				echo "\n";
			}
			$this->tags[$tag["id"]] = $tag;
		}
 		
 		if($debug_level>4)
 			print_r($this->tags);
	}
	
	
}

?>